using System.Collections.Concurrent;
using Pinwheel.Service;
using Pinwheel.Service.Protocol;

namespace Pinwheel.Kernel
{
    public sealed partial class ServiceManager : IServiceManager
    {
        private ConcurrentDictionary<string, PID> m_ServiceAliasMap = new ConcurrentDictionary<string, PID>();
        private ConcurrentDictionary<PID, string> m_AliasLookup = new ConcurrentDictionary<PID, string>();
        private ConcurrentDictionary<PID, IService> m_ServiceMaps = new ConcurrentDictionary<PID, IService>();
        
        private ConcurrentDictionary<PID, IService> m_ClientMap = new ConcurrentDictionary<PID, IService>();
        
        private ServiceManagerConfig m_Config;
        
        private IClientAgentProvider m_ClientAgentProvider;
        private IMessageQueueProvider m_MessageQueueProvider;
        
        
        public ServiceManagerConfig Config => m_Config;
        public string NodeName => m_Config.NodeName;
        
        public List<NodeConfig> NodeList => m_Config.NodeList;
        public TaskFactory Factory => TaskFactory;
        
        
        public ServiceManager(ServiceManagerConfig config)
        {
            m_Config = config;
            // Global.ServiceManager = this;
            CheckAllProvider();
            InitWorkProcess();
            InvokeBootstrap();
        }
        
        void CheckAllProvider()
        {
            if (null == m_Config.Bootstrap) throw new Exception($"Fatal error: Bootstrap not set.");
            m_ClientAgentProvider = m_Config.ClientAgentProvider;
            m_MessageQueueProvider = m_Config.MessageQueueProvider;
        }
        
        void InvokeBootstrap()
        {
            var pid = StartNewService<Cluster>("Cluster");
            Send(PID.System, pid, "StartListen", MessageType.Service, Message.Empty);
            m_Config.Bootstrap?.Start(this);
        }
        
        public PID StartNewService<TService>(string alias = null!) where TService : IServiceInstance, new()
        {
            CreateService<TService>(out var pid, alias);
            StartService(pid);
            return pid;
        }
        
        public PID CreateService<TService>(string alias = null!) where TService : IServiceInstance, new()
        {
            CreateService<TService>(out var pid, alias);
            return pid;
        }
        
        public IService CreateService<TService>(out PID pid, string alias = null!) where TService : IServiceInstance, new()
        {
            var service = new TService();
            pid = PID.Create(PID.LOCAL);
            
            var status = RegisterService(pid, service, alias);
            if (status == ServiceStatus.Registered)
            {
                service.Create(this, m_MessageQueueProvider?.Create() ?? null!);
            }
            else
            {
                pid = default;
            }

            return service;
        }
        
        public ServiceStatus RegisterService<TService>(PID pid, TService service, string? alias = null) where TService : IService
        {
            if (!m_ServiceMaps.TryAdd(pid, service))
            {
                return service.Status;
            }

            alias = string.IsNullOrEmpty(alias) ? string.Concat(nameof(TService), "_", pid.Service) : alias;
            if (!m_ServiceAliasMap.TryAdd(alias, pid) || !m_AliasLookup.TryAdd(pid, alias))
            {
                m_ServiceAliasMap.TryRemove(alias, out _);
                m_AliasLookup.TryRemove(pid, out _);
                return service.Status;
            }
            
            service.OnRegister(pid);
            
            return service.Status;
        }

        public ServiceStatus UnregisterService(PID pid)
        {
            bool find = m_ServiceMaps.TryRemove(pid, out var service);
            
            if (m_AliasLookup.TryGetValue(pid, out var alias))
            {
                m_AliasLookup.TryRemove(pid, out _);
                m_ServiceAliasMap.TryRemove(alias, out _);
            }

            if (find) service.OnUnregister();
            
            return find ? service.Status : ServiceStatus.Invalid;
        }
        
        public ServiceStatus StartService(PID pid)
        {
            if (m_ServiceMaps.TryGetValue(pid, out var service))
            {
                if (service is IServiceInstance localService)
                {
                    return StartServiceInstance(localService); 
                }
                
                if (service is IServiceAgent remoteService)
                {
                    return StartServiceAgent(remoteService);
                }
            }
            return ServiceStatus.Invalid;
        } 
        
        public ServiceStatus StartServiceInstance<TService>(TService service) where TService : IServiceInstance
        {
            service.Init();
            return service.Status; 
        }

        public ServiceStatus StartServiceAgent<TService>(TService service) where TService : IServiceAgent
        {
            throw new NotImplementedException();
        }

        public bool RenameService(PID pid, string alias)
        {
            if (m_AliasLookup.TryGetValue(pid, out var oldAlias))
            {
                if (m_ServiceAliasMap.TryGetValue(oldAlias, out _))
                {
                    m_ServiceAliasMap.TryRemove(oldAlias, out _);
                    m_ServiceAliasMap.TryAdd(alias, pid);
                }
                m_AliasLookup[pid] = alias;
                return true;
            }

            return false;
        }
        
        public bool RenameService<TService>(TService service, string alias) where TService : IServiceInstance
        {
            var kv = m_ServiceMaps.First(item=>item.Value is TService s && s.Equals(service));
            if (null! == kv.Value) return false;
            return RenameService(kv.Key, alias);
        }

        public IService GetService(PID pid)
        {
            return TryGetService(pid, out IService service) ? service : null!;
        }
        
        public PID GetServicePID(string alias)
        {
            return m_ServiceAliasMap.TryGetValue(alias, out var pid) ? pid : default;
        }
        
        public string GetServiceName(PID pid)
        {
            return m_AliasLookup.TryGetValue(pid, out var alias) ? alias : string.Empty;
        }
        
        public string GetServiceName(IService service)
        {
            var kv = m_ServiceMaps.First(item=>item.Value.Equals(service));
            return kv.Value is IService s ? GetServiceName(kv.Key) : string.Empty;
        }

        public IService GetService(string alias)
        {
            return TryGetService(alias, out IService service) ? service : null!;
        }

        public bool TryGetService<TService>(PID pid, out TService service) where TService : IService
        {
            if (m_ServiceMaps.TryGetValue(pid, out var s) && s is TService t)
            {
                service = t;
                return true;
            }
            service = default!;
            return false;
        }

        public bool TryGetService<TService>(string alias, out TService service) where TService : IService
        {
            if (m_ServiceAliasMap.TryGetValue(alias, out var pid) && m_ServiceMaps.TryGetValue(pid, out var s) && s is TService t)
            {
                service = t;
                return true;
            }
            service = default!;
            return false;
        }

        public ServiceStatus StopService(PID pid)
        {
            if (m_ServiceMaps.TryGetValue(pid, out var service))
            {
                if (service is IServiceInstance localService)
                {
                    return StopServiceInstance(localService);
                }
                
                if (service is IServiceAgent remoteService)
                {
                    return StopServiceAgent(remoteService);
                }
            }
            return ServiceStatus.Invalid;
        }

        public ServiceStatus StopServiceInstance<TService>(TService service) where TService : IServiceInstance
        {
            service.Release();
            return service.Status;
        }
        
        public ServiceStatus StopServiceAgent<TService>(TService service) where TService : IServiceAgent
        {
            service.Connect();
            return service.Status;
        }


        #region ClientAgent

        internal PID CreateClientAgent(IConnection connection)
        {
            Assert.Check(null! != m_Config.ClientAgentProvider, "Client agent provider not set.");
            var agent = m_Config.ClientAgentProvider.CreateClientAgent(this, connection);
            var pid = PID.Create(PID.LOCAL);
            
            if (!m_ClientMap.TryAdd(pid, agent))
            {
                agent.OnRegister(pid);
                return default;
            }
            
            return pid;
        }
        
        internal bool RemoveClientAgent(PID pid)
        {
            bool result = m_ClientMap.TryRemove(pid, out var service);
            if (result) service?.OnUnregister();
            return result;
        }

        #endregion
    }
}